{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "635d8ebb",
   "metadata": {},
   "source": [
    "# Agent-with-Toolkits-File-Management\n",
    "\n",
    "- Author: [Secludor](https://github.com/Secludor)\n",
    "- Peer Review: \n",
    "- Proofread : [BokyungisaGod](https://github.com/BokyungisaGod)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/15-Agent/08-Agent-with-Toolkits-File-Management.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/15-Agent/08-Agent-with-Toolkits-File-Management.ipynb)\n",
    "## Overview\n",
    "\n",
    "When configuring an agent using LangChain, one of the biggest advantages is **the integration of various features through third-party tools**.\n",
    "\n",
    "Among them, Toolkits provide a variety of integrated tools.\n",
    "\n",
    "In this tutorial, we will learn how to manage local files using the **FileManagementToolkit**.\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Supplies](#supplies)\n",
    "- [How to Use FileManagementToolkit](#how-to-use-filemanagementtoolkit)\n",
    "\n",
    "### References\n",
    "\n",
    "- [Agent Toolkits](https://api.python.langchain.com/en/latest/community/agent_toolkits.html)\n",
    "    - [FileManagementToolkit](https://api.python.langchain.com/en/latest/community/agent_toolkits/langchain_community.agent_toolkits.file_management.toolkit.FileManagementToolkit.html#langchain_community.agent_toolkits.file_management.toolkit.FileManagementToolkit)\n",
    "- [Tools](https://python.langchain.com/docs/integrations/tools/)\n",
    "    - [File System](https://python.langchain.com/docs/integrations/tools/filesystem/)\n",
    "\n",
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0854878",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "21943adb",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f25ec196",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langsmith\",\n",
    "        \"langchain\",\n",
    "        \"langchain_core\",\n",
    "        \"langchain_community\",\n",
    "        \"langchain_openai\",\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5aeaf341",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "# Attempt to load environment variables from a .env file; if unsuccessful, set them manually.\n",
    "if not load_dotenv():\n",
    "    set_env(\n",
    "        {\n",
    "            \"OPENAI_API_KEY\": \"\",\n",
    "            \"LANGCHAIN_API_KEY\": \"\",\n",
    "            \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "            \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        }\n",
    "    )\n",
    "\n",
    "# set the project name same as the title\n",
    "set_env(\n",
    "    {\n",
    "        \"LANGCHAIN_PROJECT\": \"08-Agent-with-Toolkits-File-Management\",\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa00c3f4",
   "metadata": {},
   "source": [
    "## Supplies\n",
    "These are the materials needed for the practice."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "949f1db3",
   "metadata": {},
   "source": [
    "### GoogleNews\n",
    "\n",
    "The **GoogleNews** class is the utility for fetching and parsing news from Google News RSS feeds. Here's a concise explanation of its key features:\n",
    "\n",
    "**Core Functionality**\n",
    "\n",
    "- **News Retrieval**\n",
    "    - Fetches news articles from Google News RSS feeds\n",
    "    - Supports both latest news and keyword-based searches\n",
    "    - Returns structured data in a consistent format (URL and content)\n",
    "\n",
    "- **Main Methods**\n",
    "    - ```search_latest()```: Retrieves the most recent news articles\n",
    "    - ```search_by_keyword()```: Searches news based on specific keywords\n",
    "\n",
    "**Usage Example**\n",
    "\n",
    "```python\n",
    "# Initialize the news client\n",
    "news = GoogleNews()\n",
    "\n",
    "# Get latest news\n",
    "latest = news.search_latest(k=3)\n",
    "\n",
    "# Search by keyword\n",
    "results = news.search_by_keyword(\"artificial intelligence\", k=5)\n",
    "```\n",
    "\n",
    "The class handles URL encoding, RSS feed parsing, and error cases automatically, making it ideal for integration with news monitoring systems or AI agents that need access to current news content."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "58fdf25c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import feedparser\n",
    "from urllib.parse import quote\n",
    "from typing import List, Dict, Optional\n",
    "\n",
    "\n",
    "class GoogleNews:\n",
    "    \"\"\"\n",
    "    A class for searching and retrieving Google News results.\n",
    "    Provides methods to fetch latest news and search by keywords using Google News RSS feeds.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        Initialize the GoogleNews class.\n",
    "        Sets up the base URL for Google News RSS feed.\n",
    "        \"\"\"\n",
    "        self.base_url = \"https://news.google.com/rss\"\n",
    "\n",
    "    def _fetch_news(self, url: str, k: int = 3) -> List[Dict[str, str]]:\n",
    "        \"\"\"\n",
    "        Fetch news from the given URL.\n",
    "\n",
    "        Args:\n",
    "            url (str): The URL to fetch news from\n",
    "            k (int): Maximum number of news items to fetch (default: 3)\n",
    "\n",
    "        Returns:\n",
    "            List[Dict[str, str]]: A list of dictionaries containing news titles and links\n",
    "        \"\"\"\n",
    "        news_data = feedparser.parse(url)\n",
    "        return [\n",
    "            {\"title\": entry.title, \"link\": entry.link}\n",
    "            for entry in news_data.entries[:k]\n",
    "        ]\n",
    "\n",
    "    def _collect_news(self, news_list: List[Dict[str, str]]) -> List[Dict[str, str]]:\n",
    "        \"\"\"\n",
    "         Process and organize the news list.\n",
    "\n",
    "        Args:\n",
    "            news_list (List[Dict[str, str]]): List of dictionaries containing news information\n",
    "\n",
    "        Returns:\n",
    "            List[Dict[str, str]]: List of dictionaries containing URLs and content\n",
    "        \"\"\"\n",
    "        if not news_list:\n",
    "            print(\"No news found for the given keyword.\")\n",
    "            return []\n",
    "\n",
    "        result = []\n",
    "        for news in news_list:\n",
    "            result.append({\"url\": news[\"link\"], \"content\": news[\"title\"]})\n",
    "\n",
    "        return result\n",
    "\n",
    "    def search_latest(self, k: int = 3) -> List[Dict[str, str]]:\n",
    "        \"\"\"\n",
    "        Search for the latest news.\n",
    "\n",
    "        Args:\n",
    "            k (int): Maximum number of news items to retrieve (default: 3)\n",
    "\n",
    "        Returns:\n",
    "            List[Dict[str, str]]: List of dictionaries containing URLs and content\n",
    "        \"\"\"\n",
    "        url = f\"{self.base_url}?hl=en&gl=US&ceid=US:en\"\n",
    "        news_list = self._fetch_news(url, k)\n",
    "        return self._collect_news(news_list)\n",
    "\n",
    "    def search_by_keyword(\n",
    "        self, keyword: Optional[str] = None, k: int = 3\n",
    "    ) -> List[Dict[str, str]]:\n",
    "        \"\"\"\n",
    "         Search news by keyword.\n",
    "\n",
    "        Args:\n",
    "            keyword (Optional[str]): Keyword to search for (default: None)\n",
    "            k (int): Maximum number of news items to retrieve (default: 3)\n",
    "\n",
    "        Returns:\n",
    "            List[Dict[str, str]]: List of dictionaries containing URLs and content\n",
    "        \"\"\"\n",
    "        if keyword:\n",
    "            encoded_keyword = quote(keyword)\n",
    "            url = f\"{self.base_url}/search?q={encoded_keyword}&hl=en&gl=US&ceid=US:en\"\n",
    "        else:\n",
    "            url = f\"{self.base_url}?hl=en&gl=US&ceid=US:en\"  # latest headlines\n",
    "        news_list = self._fetch_news(url, k)\n",
    "        return self._collect_news(news_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2206d778",
   "metadata": {},
   "source": [
    "### AgentStreamParser\n",
    "The AgentStreamParser is a utility class designed to handle and process the output stream from AI agents. Here's a concise explanation of its key features:\n",
    "\n",
    "**Core Functionality**\n",
    "- **Stream Processing**\n",
    "    - Parses and processes agent actions, observations, and results in real-time\n",
    "    - Handles three main types of events:\n",
    "        1. Tool calls (when agents use tools)\n",
    "        2. Observations (agent's findings)\n",
    "        3. Final results (agent's conclusions)\n",
    "\n",
    "- **Callback System**\n",
    "    - The class uses three main callbacks:\n",
    "        - Tool callbacks for monitoring tool usage\n",
    "        - Observation callbacks for tracking agent findings\n",
    "        - Result callbacks for handling final outputs\n",
    "\n",
    "**Usage Example**\n",
    "\n",
    "```python\n",
    "# Create parser with default callbacks\n",
    "parser = AgentStreamParser()\n",
    "\n",
    "# Process agent output\n",
    "parser.process_agent_steps({\n",
    "    \"actions\": [...],  # Agent actions\n",
    "    \"steps\": [...],    # Observation steps\n",
    "    \"output\": \"...\"    # Final result\n",
    "})\n",
    "```\n",
    "\n",
    "This parser is particularly useful for debugging agent behavior, logging agent actions, and maintaining a clear record of the agent's decision-making process."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "64f24906",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Any, Dict, List, Callable\n",
    "from dataclasses import dataclass\n",
    "from langchain_core.agents import AgentAction, AgentStep\n",
    "from langchain.agents.output_parsers.tools import ToolAgentAction\n",
    "\n",
    "\n",
    "# Callback function executed when a tool is called.\n",
    "def tool_callback(tool) -> None:\n",
    "    print(\"[Tool Call]\")\n",
    "    print(f\"Tool: {tool.get('tool')}\")  # Print the name of the tool used.\n",
    "    if tool_input := tool.get(\"tool_input\"):  # If there are input values for the tool\n",
    "        for k, v in tool_input.items():\n",
    "            print(f\"{k}: {v}\")  # Print the key and value of the input.\n",
    "    print(f\"Log: {tool.get('log')}\")  # Print the tool execution log.\n",
    "\n",
    "\n",
    "# Callback function to print observation results.\n",
    "def observation_callback(observation) -> None:\n",
    "    print(\"[Observation]\")\n",
    "    print(\n",
    "        f\"Observation: {observation.get('observation')}\"\n",
    "    )  # Print the observation content.\n",
    "\n",
    "\n",
    "# Callback function to print the final result.\n",
    "def result_callback(result: str) -> None:\n",
    "    print(\"[Final Answer]\")\n",
    "    print(result)  # Print the final answer.\n",
    "\n",
    "\n",
    "@dataclass\n",
    "class AgentCallbacks:\n",
    "    \"\"\"\n",
    "    A dataclass containing callback functions for the agent.\n",
    "\n",
    "    Attributes:\n",
    "        tool_callback (Callable[[Dict[str, Any]], None]): Callback function called when using tools\n",
    "        observation_callback (Callable[[Dict[str, Any]], None]): Callback function called when processing observations\n",
    "        result_callback (Callable[[str], None]): Callback function called when processing final results\n",
    "    \"\"\"\n",
    "\n",
    "    tool_callback: Callable[[Dict[str, Any]], None] = tool_callback\n",
    "    observation_callback: Callable[[Dict[str, Any]], None] = observation_callback\n",
    "    result_callback: Callable[[str], None] = result_callback\n",
    "\n",
    "\n",
    "class AgentStreamParser:\n",
    "    \"\"\"\n",
    "    A class for parsing and processing agent stream outputs.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, callbacks: AgentCallbacks = AgentCallbacks()):\n",
    "        \"\"\"\n",
    "        Initialize an AgentStreamParser object.\n",
    "\n",
    "        Args:\n",
    "            callbacks (AgentCallbacks, optional): Callback functions to use during parsing. Defaults to AgentCallbacks().\n",
    "        \"\"\"\n",
    "        self.callbacks = callbacks\n",
    "        self.output = None\n",
    "\n",
    "    def process_agent_steps(self, step: Dict[str, Any]) -> None:\n",
    "        \"\"\"\n",
    "        Process agent steps.\n",
    "\n",
    "        Args:\n",
    "            step (Dict[str, Any]): Agent step information to process\n",
    "        \"\"\"\n",
    "        if \"actions\" in step:\n",
    "            self._process_actions(step[\"actions\"])\n",
    "        elif \"steps\" in step:\n",
    "            self._process_observations(step[\"steps\"])\n",
    "        elif \"output\" in step:\n",
    "            self._process_result(step[\"output\"])\n",
    "\n",
    "    def _process_actions(self, actions: List[Any]) -> None:\n",
    "        \"\"\"\n",
    "        Process agent actions.\n",
    "\n",
    "        Args:\n",
    "            actions (List[Any]): List of actions to process\n",
    "        \"\"\"\n",
    "        for action in actions:\n",
    "            if isinstance(action, (AgentAction, ToolAgentAction)) and hasattr(\n",
    "                action, \"tool\"\n",
    "            ):\n",
    "                self._process_tool_call(action)\n",
    "\n",
    "    def _process_tool_call(self, action: Any) -> None:\n",
    "        \"\"\"\n",
    "        Process tool calls.\n",
    "\n",
    "        Args:\n",
    "            action (Any): Tool call action to process\n",
    "        \"\"\"\n",
    "        tool_action = {\n",
    "            \"tool\": getattr(action, \"tool\", None),\n",
    "            \"tool_input\": getattr(action, \"tool_input\", None),\n",
    "            \"log\": getattr(action, \"log\", None),\n",
    "        }\n",
    "        self.callbacks.tool_callback(tool_action)\n",
    "\n",
    "    def _process_observations(self, observations: List[Any]) -> None:\n",
    "        \"\"\"\n",
    "        Process observation results.\n",
    "\n",
    "        Args:\n",
    "            observations (List[Any]): List of observation results to process\n",
    "        \"\"\"\n",
    "        for observation in observations:\n",
    "            observation_dict = {}\n",
    "            if isinstance(observation, AgentStep):\n",
    "                observation_dict[\"observation\"] = getattr(\n",
    "                    observation, \"observation\", None\n",
    "                )\n",
    "            self.callbacks.observation_callback(observation_dict)\n",
    "\n",
    "    def _process_result(self, result: str) -> None:\n",
    "        \"\"\"\n",
    "        Process the final result.\n",
    "\n",
    "        Args:\n",
    "            result (str): Final result to process\n",
    "        \"\"\"\n",
    "        self.callbacks.result_callback(result)\n",
    "        self.output = result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df455d6f",
   "metadata": {},
   "source": [
    "## How to Use FileManagementToolkit\n",
    "\n",
    "**FileManagementToolkit** is a toolkit for local file management operations that:\n",
    "- Automates file management tasks\n",
    "- Enables AI agents to manipulate files safely\n",
    "- Provides comprehensive file operation tools\n",
    "\n",
    "### Security Considerations\n",
    "When using **FileManagementToolkit**, implement these security measures:\n",
    "- Limit directory access using ```root_dir```\n",
    "- Configure filesystem permissions\n",
    "- Use ```selected_tools``` to restrict available operations\n",
    "- Run agents in sandboxed environments"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de944fbc",
   "metadata": {},
   "source": [
    "### Main Components\n",
    "**File Management Tools**\n",
    "- ```CopyFileTool``` : Create a copy of a file in a specified location.\n",
    "- ```DeleteFileTool``` : Delete a file.\n",
    "- ```FileSearchTool``` : Recursively search for files in a subdirectory that match the regex pattern.\n",
    "- ```MoveFileTool``` : Move or rename a file from one location to another.\n",
    "- ```ReadFileTool``` : Read file from disk.\n",
    "- ```WriteFileTool``` : Write file to disk.\n",
    "- ```ListDirectoryTool``` : List files and directories in a specified folder.\n",
    "\n",
    "**Settings**\n",
    "- ```root_dir``` : Set the root directory of workflows.\n",
    "- ```selected_tools``` : Select the tools you want to use.\n",
    "\n",
    "**Dynamic Tool Creation**\n",
    "- ```get_tools``` : create instances of the selected tools."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "756259d8",
   "metadata": {},
   "source": [
    "### 1. Basic Setup\n",
    "The **FileManagementToolkit** provides essential file operation capabilities with security considerations. Let's explore how to set it up and use it safely."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c55877f3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[Available File Management Tools]\n",
      "- copy_file: Create a copy of a file in a specified location\n",
      "- file_delete: Delete a file\n",
      "- file_search: Recursively search for files in a subdirectory that match the regex pattern\n",
      "- move_file: Move or rename a file from one location to another\n",
      "- read_file: Read file from disk\n",
      "- write_file: Write file to disk\n",
      "- list_directory: List files and directories in a specified folder\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.agent_toolkits import FileManagementToolkit\n",
    "\n",
    "# Set the working directory to a directory named 'tmp'.\n",
    "working_directory = \"tmp\"\n",
    "\n",
    "# Create a FileManagementToolkit object.\n",
    "# Initialize toolkit with root directory\n",
    "toolkit = FileManagementToolkit(root_dir=str(working_directory))\n",
    "\n",
    "# Call the toolkit.get_tools() method to retrieve all available file management tools.\n",
    "available_tools = toolkit.get_tools()\n",
    "\n",
    "# Display available tools\n",
    "print(\"[Available File Management Tools]\")\n",
    "for tool in available_tools:\n",
    "    print(f\"- {tool.name}: {tool.description}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5eb4d832",
   "metadata": {},
   "source": [
    "**Selective Tool Access**\n",
    "-  For better security, you can restrict available tools: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "69cb77da",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[ReadFileTool(root_dir='tmp'),\n",
       " DeleteFileTool(root_dir='tmp'),\n",
       " WriteFileTool(root_dir='tmp'),\n",
       " ListDirectoryTool(root_dir='tmp')]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Initialize toolkit with selected tools only\n",
    "tools = FileManagementToolkit(\n",
    "    root_dir=str(working_directory),\n",
    "    selected_tools=[\"read_file\", \"file_delete\", \"write_file\", \"list_directory\"],\n",
    ").get_tools()\n",
    "tools"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79d6cde0",
   "metadata": {},
   "source": [
    "### 2. File Operations\n",
    " Let's explore basic file operations using the toolkit's tools. Each operation demonstrates a core file management functionality. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4fda0177",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'File written successfully to example.txt.'"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Unpack tools for easier access\n",
    "read_tool, delete_tool, write_tool, list_tool = tools\n",
    "\n",
    "\n",
    "# Create a new file with content\n",
    "write_tool.invoke({\"file_path\": \"example.txt\", \"text\": \"Hello World!\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "8214d8a5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "example.txt\n"
     ]
    }
   ],
   "source": [
    "# Check current directory contents\n",
    "print(list_tool.invoke({}))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1215bbfc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "File deleted successfully: example.txt.\n"
     ]
    }
   ],
   "source": [
    "# Remove the created file\n",
    "print(delete_tool.invoke({\"file_path\": \"example.txt\"}))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "0d7554e7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "No files found in directory .\n"
     ]
    }
   ],
   "source": [
    "# Verify file removal\n",
    "print(list_tool.invoke({}))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5d5a5c7",
   "metadata": {},
   "source": [
    "### 3. Advanced Usage (News Articles)\n",
    "Below, we combine file management with news retrieval to create and organize news articles: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "7f09593a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[CopyFileTool(root_dir='tmp'),\n",
       " DeleteFileTool(root_dir='tmp'),\n",
       " FileSearchTool(root_dir='tmp'),\n",
       " MoveFileTool(root_dir='tmp'),\n",
       " ReadFileTool(root_dir='tmp'),\n",
       " WriteFileTool(root_dir='tmp'),\n",
       " ListDirectoryTool(root_dir='tmp'),\n",
       " StructuredTool(name='latest_news', description='Look up latest news', args_schema=<class 'langchain_core.utils.pydantic.latest_news'>, func=<function latest_news at 0x000002225925F880>),\n",
       " StructuredTool(name='write_news_file', description='Write news content to a file with a sanitized filename.\\n\\nArgs:\\n    title: News title\\n    content: News content\\n    url: News URL\\nReturns:\\n    str: Result message', args_schema=<class 'langchain_core.utils.pydantic.write_news_file'>, func=<function write_news_file at 0x000002225925E700>)]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.tools import tool\n",
    "from typing import List, Dict\n",
    "import re\n",
    "\n",
    "\n",
    "# Function to clean filenames by removing invalid characters and making them filesystem-safe\n",
    "def sanitize_filename(filename: str) -> str:\n",
    "    \"\"\"\n",
    "    Sanitize filename by removing invalid characters.\n",
    "\n",
    "    Args:\n",
    "        filename (str): Original filename\n",
    "    Returns:\n",
    "        str: Sanitized filename\n",
    "    \"\"\"\n",
    "    # Remove or replace invalid characters with underscore\n",
    "    sanitized = re.sub(r'[<>:\"/\\\\|?*]', \"_\", filename)\n",
    "    # Remove leading/trailing periods or spaces for additional safety\n",
    "    sanitized = sanitized.strip(\". \")\n",
    "    return sanitized\n",
    "\n",
    "\n",
    "# Tool for writing news articles to files with proper formatting and sanitized filenames\n",
    "@tool\n",
    "def write_news_file(title: str, content: str, url: str) -> str:\n",
    "    \"\"\"\n",
    "    Write news content to a file with a sanitized filename.\n",
    "\n",
    "    Args:\n",
    "        title: News title\n",
    "        content: News content\n",
    "        url: News URL\n",
    "    Returns:\n",
    "        str: Result message\n",
    "    \"\"\"\n",
    "    # Create safe filename from the title\n",
    "    safe_filename = sanitize_filename(title) + \".txt\"\n",
    "\n",
    "    # Format the content with title, body, and URL\n",
    "    formatted_content = f\"Title: {title}\\nContent: {content}\\nURL: {url}\"\n",
    "\n",
    "    # Get the write tool and create the file\n",
    "    write_tool = next(tool for tool in tools if tool.name == \"write_file\")\n",
    "    write_tool.invoke({\"file_path\": safe_filename, \"text\": formatted_content})\n",
    "\n",
    "    return f\"Created file: {safe_filename}\"\n",
    "\n",
    "\n",
    "# Define the latest news search tool.\n",
    "@tool\n",
    "def latest_news(k: int = 5) -> List[Dict[str, str]]:\n",
    "    \"\"\"Look up latest news\"\"\"\n",
    "    # Create a GoogleNews object.\n",
    "    news_tool = GoogleNews()\n",
    "    # Search for the latest news and return the results. k indicates the number of news items to return.\n",
    "    return news_tool.search_latest(k=k)\n",
    "\n",
    "\n",
    "# Use the FileManagementToolkit to retrieve file management tools.\n",
    "tools = FileManagementToolkit(\n",
    "    root_dir=str(working_directory),\n",
    "    selected_tools=[\n",
    "        \"copy_file\",\n",
    "        \"file_delete\",\n",
    "        \"file_search\",\n",
    "        \"move_file\",\n",
    "        \"read_file\",\n",
    "        \"write_file\",\n",
    "        \"list_directory\",\n",
    "    ],\n",
    ").get_tools()\n",
    "\n",
    "# Add custom tools\n",
    "tools.extend([latest_news, write_news_file])\n",
    "\n",
    "# Confirm which tools are available\n",
    "tools"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f730d72",
   "metadata": {},
   "source": [
    " Set up the agent with appropriate tools and configuration: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2f3ae236",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.agents import create_tool_calling_agent, AgentExecutor\n",
    "from langchain_community.chat_message_histories import ChatMessageHistory\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "\n",
    "# Dictionary to store session history\n",
    "store = {}\n",
    "\n",
    "# Create a prompt describing tool usage\n",
    "# The prompt provides the agent with text describing the tasks the model should perform (names and roles of tools).\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are a helpful assistant. \"\n",
    "            \"Make sure to use the `latest_news` tool to find latest news. \"\n",
    "            \"For each article, use the `write_news_file` tool to save it safely. \"\n",
    "            \"The write_news_file tool will automatically handle filename sanitization.\",\n",
    "        ),\n",
    "        (\"placeholder\", \"{chat_history}\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "        (\"placeholder\", \"{agent_scratchpad}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# Create the LLM\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "\n",
    "# Create an agent configured to call the tools\n",
    "agent = create_tool_calling_agent(llm, tools, prompt)\n",
    "\n",
    "# AgentExecutor for structured operation\n",
    "agent_executor = AgentExecutor(\n",
    "    agent=agent,\n",
    "    tools=tools,\n",
    "    verbose=False,\n",
    "    handle_parsing_errors=True,\n",
    ")\n",
    "\n",
    "\n",
    "# Retrieve or create a session's chat history\n",
    "def get_session_history(session_ids):\n",
    "    if session_ids not in store:  # If session_id is not in store\n",
    "        # Create a new ChatMessageHistory object and store it\n",
    "        store[session_ids] = ChatMessageHistory()\n",
    "    return store[session_ids]  # Return the session history for the specified session ID\n",
    "\n",
    "\n",
    "# Create a runnable agent with a chat history\n",
    "agent_with_chat_history = RunnableWithMessageHistory(\n",
    "    agent_executor,\n",
    "    # Chat session_id\n",
    "    get_session_history,\n",
    "    # Key for input messages in the prompt\n",
    "    input_messages_key=\"input\",\n",
    "    # Key for history messages in the prompt\n",
    "    history_messages_key=\"chat_history\",\n",
    ")\n",
    "\n",
    "agent_stream_parser = AgentStreamParser()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc0fe865",
   "metadata": {},
   "source": [
    "**Example Operations**\n",
    "\n",
    "1. Fetch and Save News"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d186487",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent Execution Result:\n",
      "[Tool Call]\n",
      "Tool: latest_news\n",
      "k: 5\n",
      "Log: \n",
      "Invoking: `latest_news` with `{'k': 5}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: [{'url': 'https://news.google.com/rss/articles/CBMipwFBVV95cUxNMjdwdGtGZ1BHUTAza0d4Q25YTWRTeTl6ZFExN2VaOXZMTUV1UXdkeEFMTmdsV3VqZHBFTXp2ZkxxakcwY1l2bFpyQmQ5SElSRTdYRjYtLUZFV2tnOWMxVUtsbjVTSVMwTDBUbGl4Y2U4UnNsU2JDVDNWQ0Y0aVhuOWpxdVpwVWNTcDNjeHRRMElJQzM1YkFoM05KX2pWdFNjdVk3VUhqcw?oc=5', 'content': 'Question on ASEAN stumped Hegseth at Senate hearing. What is it and why is it important? - The Associated Press'}, {'url': 'https://news.google.com/rss/articles/CBMiswFBVV95cUxPVnNhNDh4aHpfLTJ5SlJqbkcxa3hLSGxMQUdKNm1FVlRyMDROZXp5U0hQRGhKSDFWXzBtYXhBemZESFdfcjFsOHgtOVdJd3BFQm13c3FyMnRYUWpxV19RNVVsVzJIbUVzbDlSWGxBSWlmX1pkVEV1RXhTWmdIYWJZdTJOZHE4VU5lc1ZHNDhJMmFiWGw5R19HQVRDWEJndGhpYVpOQXlPejlMUTJ4V3ZUcTFGYw?oc=5', 'content': 'Live updates: Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn - CNN'}, {'url': 'https://news.google.com/rss/articles/CBMimgFBVV95cUxQdDAycFllRjA4dzRFbDZxUDZDbklKaEJkcEoyVG5GMUZscndoamY5Z24zMVpBLWZ6b2hOQThMNUVCUDdJOFNjM082MTBfR0ZHMjFMbUJybGRUYnlQLUxqRk9jbHpHQ0hRR2xCR2NoMExOMHh3WjRiSktPbldSTmlsbFNYVnlBMlYtWTdFaVhKcTg4em14WC1aSFl3?oc=5', 'content': 'The great social media migration: Sudden influx of US users to RedNote connects Chinese and Americans like never before - CNN'}, {'url': 'https://news.google.com/rss/articles/CBMigAFBVV95cUxPMXJ6aDBPcmlsOVhyeFU2TzNBZExzNVR2NUZVTXhhVVJoX0xiWUtKa1BqbzN2cHVXNm1kMjkyMmRxWWZOWDg3WEtYbGthMmxHcFJfcXNFVDJvZ05hV0V0ckRNRXBIWHZMOXlybFFlekVkWEQzdk5FN21XZXhGRWs0ZA?oc=5', 'content': \"South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff - Axios\"}, {'url': 'https://news.google.com/rss/articles/CBMinwFBVV95cUxNVGJxWEdTM2w2Y3piNWlmRzliTk5JcHhoN1VhVGpSa2RXckV2bnkxVzVEcjFWb0R4Qkdxbnp3MEUtRDRaLWNIanQyYm00ZGdWM3hqdlNEZXYtTzNydWVMQUVDMFZ1VldGemFiRWNVX0NYLUlYY1ZYRGU0ajlrZnFPSVhXckwwR1JqSEYxUlNDc3ZXNU1DNS1HMFNLTi1BVDA?oc=5', 'content': 'Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights - The Associated Press'}]\n",
      "[Tool Call]\n",
      "Tool: write_news_file\n",
      "title: Question on ASEAN stumped Hegseth at Senate hearing\n",
      "content: Question on ASEAN stumped Hegseth at Senate hearing. What is it and why is it important? - The Associated Press\n",
      "url: https://news.google.com/rss/articles/CBMipwFBVV95cUxNMjdwdGtGZ1BHUTAza0d4Q25YTWRTeTl6ZFExN2VaOXZMTUV1UXdkeEFMTmdsV3VqZHBFTXp2ZkxxakcwY1l2bFpyQmQ5SElSRTdYRjYtLUZFV2tnOWMxVUtsbjVTSVMwTDBUbGl4Y2U4UnNsU2JDVDNWQ0Y0aVhuOWpxdVpwVWNTcDNjeHRRMElJQzM1YkFoM05KX2pWdFNjdVk3VUhqcw?oc=5\n",
      "Log: \n",
      "Invoking: `write_news_file` with `{'title': 'Question on ASEAN stumped Hegseth at Senate hearing', 'content': 'Question on ASEAN stumped Hegseth at Senate hearing. What is it and why is it important? - The Associated Press', 'url': 'https://news.google.com/rss/articles/CBMipwFBVV95cUxNMjdwdGtGZ1BHUTAza0d4Q25YTWRTeTl6ZFExN2VaOXZMTUV1UXdkeEFMTmdsV3VqZHBFTXp2ZkxxakcwY1l2bFpyQmQ5SElSRTdYRjYtLUZFV2tnOWMxVUtsbjVTSVMwTDBUbGl4Y2U4UnNsU2JDVDNWQ0Y0aVhuOWpxdVpwVWNTcDNjeHRRMElJQzM1YkFoM05KX2pWdFNjdVk3VUhqcw?oc=5'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: write_news_file\n",
      "title: Live updates: Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn\n",
      "content: Live updates: Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn - CNN\n",
      "url: https://news.google.com/rss/articles/CBMiswFBVV95cUxPVnNhNDh4aHpfLTJ5SlJqbkcxa3hLSGxMQUdKNm1FVlRyMDROZXp5U0hQRGhKSDFWXzBtYXhBemZESFdfcjFsOHgtOVdJd3BFQm13c3FyMnRYUWpxV19RNVVsVzJIbUVzbDlSWGxBSWlmX1pkVEV1RXhTWmdIYWJZdTJOZHE4VU5lc1ZHNDhJMmFiWGw5R19HQVRDWEJndGhpYVpOQXlPejlMUTJ4V3ZUcTFGYw?oc=5\n",
      "Log: \n",
      "Invoking: `write_news_file` with `{'title': 'Live updates: Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn', 'content': 'Live updates: Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn - CNN', 'url': 'https://news.google.com/rss/articles/CBMiswFBVV95cUxPVnNhNDh4aHpfLTJ5SlJqbkcxa3hLSGxMQUdKNm1FVlRyMDROZXp5U0hQRGhKSDFWXzBtYXhBemZESFdfcjFsOHgtOVdJd3BFQm13c3FyMnRYUWpxV19RNVVsVzJIbUVzbDlSWGxBSWlmX1pkVEV1RXhTWmdIYWJZdTJOZHE4VU5lc1ZHNDhJMmFiWGw5R19HQVRDWEJndGhpYVpOQXlPejlMUTJ4V3ZUcTFGYw?oc=5'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: write_news_file\n",
      "title: The great social media migration: Sudden influx of US users to RedNote connects Chinese and Americans like never before\n",
      "content: The great social media migration: Sudden influx of US users to RedNote connects Chinese and Americans like never before - CNN\n",
      "url: https://news.google.com/rss/articles/CBMimgFBVV95cUxQdDAycFllRjA4dzRFbDZxUDZDbklKaEJkcEoyVG5GMUZscndoamY5Z24zMVpBLWZ6b2hOQThMNUVCUDdJOFNjM082MTBfR0ZHMjFMbUJybGRUYnlQLUxqRk9jbHpHQ0hRR2xCR2NoMExOMHh3WjRiSktPbldSTmlsbFNYVnlBMlYtWTdFaVhKcTg4em14WC1aSFl3?oc=5\n",
      "Log: \n",
      "Invoking: `write_news_file` with `{'title': 'The great social media migration: Sudden influx of US users to RedNote connects Chinese and Americans like never before', 'content': 'The great social media migration: Sudden influx of US users to RedNote connects Chinese and Americans like never before - CNN', 'url': 'https://news.google.com/rss/articles/CBMimgFBVV95cUxQdDAycFllRjA4dzRFbDZxUDZDbklKaEJkcEoyVG5GMUZscndoamY5Z24zMVpBLWZ6b2hOQThMNUVCUDdJOFNjM082MTBfR0ZHMjFMbUJybGRUYnlQLUxqRk9jbHpHQ0hRR2xCR2NoMExOMHh3WjRiSktPbldSTmlsbFNYVnlBMlYtWTdFaVhKcTg4em14WC1aSFl3?oc=5'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: write_news_file\n",
      "title: South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff\n",
      "content: South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff - Axios\n",
      "url: https://news.google.com/rss/articles/CBMigAFBVV95cUxPMXJ6aDBPcmlsOVhyeFU2TzNBZExzNVR2NUZVTXhhVVJoX0xiWUtKa1BqbzN2cHVXNm1kMjkyMmRxWWZOWDg3WEtYbGthMmxHcFJfcXNFVDJvZ05hV0V0ckRNRXBIWHZMOXlybFFlekVkWEQzdk5FN21XZXhGRWs0ZA?oc=5\n",
      "Log: \n",
      "Invoking: `write_news_file` with `{'title': \"South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff\", 'content': \"South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff - Axios\", 'url': 'https://news.google.com/rss/articles/CBMigAFBVV95cUxPMXJ6aDBPcmlsOVhyeFU2TzNBZExzNVR2NUZVTXhhVVJoX0xiWUtKa1BqbzN2cHVXNm1kMjkyMmRxWWZOWDg3WEtYbGthMmxHcFJfcXNFVDJvZ05hV0V0ckRNRXBIWHZMOXlybFFlekVkWEQzdk5FN21XZXhGRWs0ZA?oc=5'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: write_news_file\n",
      "title: Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights\n",
      "content: Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights - The Associated Press\n",
      "url: https://news.google.com/rss/articles/CBMinwFBVV95cUxNVGJxWEdTM2w2Y3piNWlmRzliTk5JcHhoN1VhVGpSa2RXckV2bnkxVzVEcjFWb0R4Qkdxbnp3MEUtRDRaLWNIanQyYm00ZGdWM3hqdlNEZXYtTzNydWVMQUVDMFZ1VldGemFiRWNVX0NYLUlYY1ZYRGU0ajlrZnFPSVhXckwwR1JqSEYxUlNDc3ZXNU1DNS1HMFNLTi1BVDA?oc=5\n",
      "Log: \n",
      "Invoking: `write_news_file` with `{'title': 'Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights', 'content': 'Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights - The Associated Press', 'url': 'https://news.google.com/rss/articles/CBMinwFBVV95cUxNVGJxWEdTM2w2Y3piNWlmRzliTk5JcHhoN1VhVGpSa2RXckV2bnkxVzVEcjFWb0R4Qkdxbnp3MEUtRDRaLWNIanQyYm00ZGdWM3hqdlNEZXYtTzNydWVMQUVDMFZ1VldGemFiRWNVX0NYLUlYY1ZYRGU0ajlrZnFPSVhXckwwR1JqSEYxUlNDc3ZXNU1DNS1HMFNLTi1BVDA?oc=5'}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: Created file: Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "[Observation]\n",
      "Observation: Created file: Live updates_ Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "[Observation]\n",
      "Observation: Created file: The great social media migration_ Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "[Observation]\n",
      "Observation: Created file: South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "[Observation]\n",
      "Observation: Created file: Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "[Final Answer]\n",
      "I have successfully created files for the latest 5 news articles. Here are the titles of the files:\n",
      "\n",
      "1. **Question on ASEAN stumped Hegseth at Senate hearing.txt**\n",
      "2. **Live updates: Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt**\n",
      "3. **The great social media migration: Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt**\n",
      "4. **South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt**\n",
      "5. **Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt**\n",
      "\n",
      "Each file contains the respective article's content and URL. If you need any further assistance, feel free to ask!\n"
     ]
    }
   ],
   "source": [
    "# Request the agent to fetch and store news articles\n",
    "result = agent_with_chat_history.stream(\n",
    "    {\n",
    "        \"input\": \"Search for the latest 5 news articles, create a file for each news article with the title as the filename (.txt), \"\n",
    "        \"and include the content and URL of the news in the file.\"\n",
    "    },\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "\n",
    "\n",
    "print(\"Agent Execution Result:\")\n",
    "\n",
    "for step in result:\n",
    "\n",
    "    agent_stream_parser.process_agent_steps(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a098b487",
   "metadata": {},
   "source": [
    "You can check the contents of the ```tmp``` folder and see that the files have been created as shown below.\n",
    "\n",
    "![08-agent-with-toolkits-file-management-demonstration-00](./assets/08-agent-with-toolkits-file-management-demonstration-00.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "532d9730",
   "metadata": {},
   "source": [
    "2. Modify Filenames"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "7d785d1a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent Execution Result:\n",
      "[Tool Call]\n",
      "Tool: list_directory\n",
      "Log: \n",
      "Invoking: `list_directory` with `{}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: Live updates_ Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "The great social media migration_ Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "[Tool Call]\n",
      "Tool: move_file\n",
      "source_path: Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "destination_path: 🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "Log: \n",
      "Invoking: `move_file` with `{'source_path': 'Question on ASEAN stumped Hegseth at Senate hearing.txt', 'destination_path': '🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: move_file\n",
      "source_path: Live updates_ Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "destination_path: 🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "Log: \n",
      "Invoking: `move_file` with `{'source_path': 'Live updates_ Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt', 'destination_path': '🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: move_file\n",
      "source_path: The great social media migration_ Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "destination_path: 🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "Log: \n",
      "Invoking: `move_file` with `{'source_path': 'The great social media migration_ Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt', 'destination_path': '🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: move_file\n",
      "source_path: South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "destination_path: 🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "Log: \n",
      "Invoking: `move_file` with `{'source_path': \"South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\", 'destination_path': \"🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\"}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: move_file\n",
      "source_path: Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "destination_path: ⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "Log: \n",
      "Invoking: `move_file` with `{'source_path': 'Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt', 'destination_path': '⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: File moved successfully from Question on ASEAN stumped Hegseth at Senate hearing.txt to 🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt.\n",
      "[Observation]\n",
      "Observation: File moved successfully from Live updates_ Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt to 🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt.\n",
      "[Observation]\n",
      "Observation: File moved successfully from The great social media migration_ Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt to 🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt.\n",
      "[Observation]\n",
      "Observation: File moved successfully from South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt to 🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt.\n",
      "[Observation]\n",
      "Observation: File moved successfully from Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt to ⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt.\n",
      "[Final Answer]\n",
      "The filenames of the news articles have been successfully updated with suitable emojis at the beginning. Here are the new filenames:\n",
      "\n",
      "1. **🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt**\n",
      "2. **🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt**\n",
      "3. **🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt**\n",
      "4. **🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt**\n",
      "5. **⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt**\n",
      "\n",
      "If you need any more changes or assistance, feel free to let me know!\n"
     ]
    }
   ],
   "source": [
    "result = agent_with_chat_history.stream(\n",
    "    {\n",
    "        \"input\": \"Change the filenames of the previously created files by adding a suitable emoji at the beginning of the title. \"\n",
    "        \"Also, make sure the filenames are neat.\"\n",
    "    },\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "\n",
    "print(\"Agent Execution Result:\")\n",
    "for step in result:\n",
    "    agent_stream_parser.process_agent_steps(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28805f8c",
   "metadata": {},
   "source": [
    "You can check the contents of the ```tmp``` folder and see that the filenames have been changed as shown below.\n",
    "\n",
    "![08-agent-with-toolkits-file-management-demonstration-01](./assets/08-agent-with-toolkits-file-management-demonstration-01.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2f72883",
   "metadata": {},
   "source": [
    "3. Organize Files"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "70c86754",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent Execution Result:\n",
      "[Tool Call]\n",
      "Tool: list_directory\n",
      "Log: \n",
      "Invoking: `list_directory` with `{}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: ⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "[Tool Call]\n",
      "Tool: write_file\n",
      "file_path: news/.keep\n",
      "text: \n",
      "append: False\n",
      "Log: \n",
      "Invoking: `write_file` with `{'file_path': 'news/.keep', 'text': '', 'append': False}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: File written successfully to news/.keep.\n",
      "[Tool Call]\n",
      "Tool: copy_file\n",
      "source_path: 🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "destination_path: news/🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "Log: \n",
      "Invoking: `copy_file` with `{'source_path': '🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt', 'destination_path': 'news/🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: copy_file\n",
      "source_path: 🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "destination_path: news/🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "Log: \n",
      "Invoking: `copy_file` with `{'source_path': '🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt', 'destination_path': 'news/🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: copy_file\n",
      "source_path: 🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "destination_path: news/🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "Log: \n",
      "Invoking: `copy_file` with `{'source_path': '🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt', 'destination_path': 'news/🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: copy_file\n",
      "source_path: 🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "destination_path: news/🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "Log: \n",
      "Invoking: `copy_file` with `{'source_path': \"🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\", 'destination_path': \"news/🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\"}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: copy_file\n",
      "source_path: ⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "destination_path: news/⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "Log: \n",
      "Invoking: `copy_file` with `{'source_path': '⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt', 'destination_path': 'news/⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: File copied successfully from 🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt to news/🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt.\n",
      "[Observation]\n",
      "Observation: File copied successfully from 🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt to news/🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt.\n",
      "[Observation]\n",
      "Observation: File copied successfully from 🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt to news/🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt.\n",
      "[Observation]\n",
      "Observation: File copied successfully from 🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt to news/🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt.\n",
      "[Observation]\n",
      "Observation: File copied successfully from ⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt to news/⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt.\n",
      "[Final Answer]\n",
      "I have created a `news` folder and successfully copied all the previously created news files into that folder. The contents have been preserved in the copied files. If you need any further assistance or modifications, just let me know!\n"
     ]
    }
   ],
   "source": [
    "result = agent_with_chat_history.stream(\n",
    "    {\n",
    "        \"input\": \"Create a `news` folder and then copy all previously created files into that folder. \"\n",
    "        \"Ensure that the contents are copied as well.\"\n",
    "    },\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "\n",
    "print(\"Agent Execution Result:\")\n",
    "for step in result:\n",
    "    agent_stream_parser.process_agent_steps(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f3c33e6",
   "metadata": {},
   "source": [
    "You can check the contents of the ```tmp``` folder and see that the ```news``` folder has been created and the files have been copied as shown below.\n",
    "\n",
    "![08-agent-with-toolkits-file-management-demonstration-02](./assets/08-agent-with-toolkits-file-management-demonstration-02.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "e270a514",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent Execution Result:\n",
      "[Tool Call]\n",
      "Tool: file_search\n",
      "pattern: *.txt\n",
      "Log: \n",
      "Invoking: `file_search` with `{'pattern': '*.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: ⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "news\\⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "news\\🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "news\\🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "news\\🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "news\\🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "[Tool Call]\n",
      "Tool: file_delete\n",
      "file_path: ⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt\n",
      "Log: \n",
      "Invoking: `file_delete` with `{'file_path': '⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: file_delete\n",
      "file_path: 🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\n",
      "Log: \n",
      "Invoking: `file_delete` with `{'file_path': \"🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt\"}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: file_delete\n",
      "file_path: 🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt\n",
      "Log: \n",
      "Invoking: `file_delete` with `{'file_path': '🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: file_delete\n",
      "file_path: 🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt\n",
      "Log: \n",
      "Invoking: `file_delete` with `{'file_path': '🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Tool Call]\n",
      "Tool: file_delete\n",
      "file_path: 🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt\n",
      "Log: \n",
      "Invoking: `file_delete` with `{'file_path': '🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt'}`\n",
      "\n",
      "\n",
      "\n",
      "[Observation]\n",
      "Observation: File deleted successfully: ⚖️ Supreme Court will hear Texas anti-pornography law that challengers say violates free-speech rights.txt.\n",
      "[Observation]\n",
      "Observation: File deleted successfully: 🇰🇷 South Korea's impeached president Yoon Suk Yeol arrested after hours-long standoff.txt.\n",
      "[Observation]\n",
      "Observation: File deleted successfully: 🇸🇬 Question on ASEAN stumped Hegseth at Senate hearing.txt.\n",
      "[Observation]\n",
      "Observation: File deleted successfully: 🌐 The great social media migration Sudden influx of US users to RedNote connects Chinese and Americans like never before.txt.\n",
      "[Observation]\n",
      "Observation: File deleted successfully: 🔥 Live updates Los Angeles wildfires, Palisades and Eaton fires, Auto fire in Ventura continue to burn.txt.\n",
      "[Final Answer]\n",
      "All .txt files outside of the `news` folder have been successfully deleted. The files in the `news` folder remain intact. If you need any further assistance or modifications, feel free to ask!\n"
     ]
    }
   ],
   "source": [
    "result = agent_with_chat_history.stream(\n",
    "    {\"input\": \"Delete all .txt files except for those in the news folder.\"},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "\n",
    "print(\"Agent Execution Result:\")\n",
    "for step in result:\n",
    "    agent_stream_parser.process_agent_steps(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70c5d0ed",
   "metadata": {},
   "source": [
    "You can check the contents of the ```tmp``` folder and see that all files except for the ```news``` folder have been deleted, as shown below.\n",
    "\n",
    "![08-agent-with-toolkits-file-management-demonstration-03](./assets/08-agent-with-toolkits-file-management-demonstration-03.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0025f68",
   "metadata": {},
   "source": [
    "### 4. Error Handling Examples\n",
    "To ensure robust file operations, wrap your tool calls with a safe operation function: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "a284bc3e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing to protected directory: File written successfully to protected/example.txt.\n",
      "Reading non-existent file: Error: no such file or directory: nonexistent.txt\n"
     ]
    }
   ],
   "source": [
    "def safe_file_operation(operation_func, **kwargs):\n",
    "    \"\"\"\n",
    "    Wrapper function for safe file operations with comprehensive error handling.\n",
    "    \"\"\"\n",
    "    try:\n",
    "        # Tools expect a single input dictionary\n",
    "        return operation_func(input=kwargs)\n",
    "    except FileNotFoundError:\n",
    "        return \"Error: File not found\"\n",
    "    except PermissionError:\n",
    "        return \"Error: Permission denied\"\n",
    "    except IsADirectoryError:\n",
    "        return \"Error: Path is a directory, not a file\"\n",
    "    except Exception as e:\n",
    "        return f\"Unexpected error: {str(e)}\"\n",
    "\n",
    "\n",
    "# Example usage with correct argument structure\n",
    "def demonstrate_file_operations():\n",
    "    # Writing to a protected directory\n",
    "    result1 = safe_file_operation(\n",
    "        write_tool.invoke, file_path=\"protected/example.txt\", text=\"Test content\"\n",
    "    )\n",
    "    print(f\"Writing to protected directory: {result1}\")\n",
    "\n",
    "    # Reading non-existent file\n",
    "    result2 = safe_file_operation(read_tool.invoke, file_path=\"nonexistent.txt\")\n",
    "    print(f\"Reading non-existent file: {result2}\")\n",
    "\n",
    "\n",
    "# Run the demonstrations\n",
    "demonstrate_file_operations()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
